package com.cloud.logs;

import cn.hutool.core.util.ObjectUtil;
import com.alibaba.fastjson.JSONObject;
import com.cloud.domain.LoginUser;
import com.cloud.entity.SysOperLogs;
import com.cloud.enums.HttpMethod;
import com.cloud.service.FeignSystemService;
import com.cloud.utils.SecurityUtils;
import com.cloud.utils.ServletUtils;
import com.cloud.utils.SpringUtils;
import com.cloud.utils.StringUtils;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import org.aspectj.lang.JoinPoint;
import org.aspectj.lang.annotation.AfterReturning;
import org.aspectj.lang.annotation.Aspect;
import org.aspectj.lang.annotation.Pointcut;
import org.aspectj.lang.reflect.MethodSignature;
import org.springframework.stereotype.Component;
import org.springframework.validation.BindingResult;
import org.springframework.web.context.request.RequestAttributes;
import org.springframework.web.context.request.RequestContextHolder;
import org.springframework.web.multipart.MultipartFile;

import java.lang.reflect.Method;
import java.util.Collection;
import java.util.Date;
import java.util.Map;

/**
 * 切面处理类,操作日志异常日志记录处理
 *
 * @author yzj
 */
@Aspect
@Component
public class LogsAspect {

    /**
     * 设置操作日志切入点
     */
    @Pointcut("@annotation(com.cloud.logs.Logs)")
    public void logCutPoint() {
    }

    /**
     * 正常返回通知,拦截用户操作日志,连接点正常执行完成后执行,如果连接点抛出异常,则不会执行
     *
     * @param joinPoint 切入点
     * @param result    返回结果
     */
    @AfterReturning(value = "logCutPoint()", returning = "result")
    public void saveActionLogs(JoinPoint joinPoint, Object result) {
        RequestAttributes requestAttributes = RequestContextHolder.getRequestAttributes();
        HttpServletRequest request = (HttpServletRequest) requestAttributes.resolveReference(RequestAttributes.REFERENCE_REQUEST);
        SysOperLogs operLogs = new SysOperLogs();
        try {
            // 从切面织入点处通过反射机制获取织入点处的方法
            MethodSignature signature = (MethodSignature) joinPoint.getSignature();
            Method method = signature.getMethod();
            Logs opLogs = method.getAnnotation(Logs.class);
            if (opLogs != null) {
                LoginUser loginUser = SecurityUtils.getLoginUser();
                operLogs.setSysCode(loginUser.getSysCode());
                operLogs.setTitle(opLogs.title());
                operLogs.setBusinessType(opLogs.businessType().ordinal());
                operLogs.setRequestMethod(opLogs.requestMethod());
                operLogs.setOperDevice(loginUser.getLoginDevice());
                if (StringUtils.isNotEmpty(opLogs.method())) {
                    operLogs.setMethod(opLogs.method());
                } else {
                    operLogs.setMethod(request.getMethod());
                }
                operLogs.setOperName(loginUser.getNickName());
            }
            operLogs.setOperUrl(request.getRequestURI());
            operLogs.setOperIp(ServletUtils.getClientIPAddress(request));
            // 获取接口请求参数
            operLogs.setJsonParam(argsArrayToString(joinPoint.getArgs()));
            operLogs.setJsonResult(JSONObject.toJSONString(result));
            operLogs.setOperTime(new Date());
            if (HttpMethod.PUT.name().equals(operLogs.getMethod()) || HttpMethod.POST.name().equals(operLogs.getMethod())) {
                SpringUtils.getBean(FeignSystemService.class).saveOperLogs(operLogs);
            }
        } catch (Exception e) {
            e.printStackTrace();
        }
    }

    private String argsArrayToString(Object[] params) {
        StringBuilder builder = new StringBuilder();
        if (params != null && params.length > 0) {
            for (Object o : params) {
                if (ObjectUtil.isNotNull(o) && !isFilterObject(o)) {
                    try {
                        builder.append(JSONObject.toJSONString(o)).append(" ");
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
            }
        }
        return builder.toString();
    }

    /**
     * 判断是否需要过滤的对象。
     *
     * @param object 对象信息。
     * @return 如果是需要过滤的对象, 则返回true;否则返回false
     */
    @SuppressWarnings("rawtypes")
    private boolean isFilterObject(final Object object) {
        Class<?> clazz = object.getClass();
        if (clazz.isArray()) {
            return clazz.getComponentType().isAssignableFrom(MultipartFile.class);
        } else if (Collection.class.isAssignableFrom(clazz)) {
            Collection collection = (Collection) object;
            for (Object value : collection) {
                return value instanceof MultipartFile;
            }
        } else if (Map.class.isAssignableFrom(clazz)) {
            Map map = (Map) object;
            for (Object value : map.entrySet()) {
                Map.Entry entry = (Map.Entry) value;
                return entry.getValue() instanceof MultipartFile;
            }
        }
        return object instanceof MultipartFile || object instanceof HttpServletRequest
                || object instanceof HttpServletResponse || object instanceof BindingResult;
    }

}